package com.ciandt.techgallery.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import com.ciandt.techgallery.persistence.dao.TechGalleryUserDAO;
import com.ciandt.techgallery.persistence.dao.impl.TechGalleryUserDAOImpl;
import com.ciandt.techgallery.persistence.model.TechGalleryUser;
import com.ciandt.techgallery.service.UserServiceTG;
import com.ciandt.techgallery.service.enums.ValidationMessageEnums;
import com.ciandt.techgallery.service.impl.profile.UserProfileServiceImpl;
import com.ciandt.techgallery.service.model.Response;
import com.ciandt.techgallery.service.model.UserResponse;
import com.ciandt.techgallery.service.model.UsersResponse;
import com.ciandt.techgallery.utils.i18n.I18n;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.server.spi.response.BadRequestException;
import com.google.api.server.spi.response.InternalServerErrorException;
import com.google.api.server.spi.response.NotFoundException;
import com.google.api.services.plus.Plus;
import com.google.api.services.plus.model.Person;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.users.User;
public class UserServiceTGImpl implements UserServiceTG {
/*
* Constants --------------------------------------------
*/
private static final String PEOPLE_ENDPOINT_PROFILE = "https://people.cit.com.br/profile/";
private static final String PEOPLE_ENDPOINT_SEARCH = "https://people.cit.com.br/search/json/?q=";
private static final String EMAIL_DOMAIN = "@ciandt.com";
private static final int INDEX_PEOPLE_API_NAME = 0;
private static final int INDEX_PEOPLE_API_LOGIN = 1;
private static final String OPERATION_FAILED = "Operation failed";
private static final Logger log = Logger.getLogger(UserServiceTGImpl.class.getName());
private static final I18n i18n = I18n.getInstance();
/*
* Attributes --------------------------------------------
*/
private static UserServiceTGImpl instance;
private MemcacheService syncCache = MemcacheServiceFactory.getMemcacheService();
private final Expiration memCacheTimeExpliration = Expiration.byDeltaSeconds(7200);
TechGalleryUserDAO userDao = TechGalleryUserDAOImpl.getInstance();
/*
* Constructors --------------------------------------------
*/
private UserServiceTGImpl() {
}
/**
* For singleton
*
* @return the current instance, if it exists. The recently created instance,
* if not.
*/
/**
* Singleton method for the service.
*
* @author <a href="mailto:joaom@ciandt.com"> João Felipe de Medeiros
* Moreira </a>
* @since 08/10/2015
*
* @return UserServiceTGImpl instance.
*/
public static UserServiceTGImpl getInstance() {
if (instance == null) {
instance = new UserServiceTGImpl();
}
return instance;
}
/*
* Methods --------------------------------------------
*/
/**
* Gets all users from the datastore.
*/
@Override
public Response getUsers() throws NotFoundException {
List<TechGalleryUser> userEntities = userDao.findAll();
// if user list is null, return a not found exception
if (userEntities == null) {
throw new NotFoundException(OPERATION_FAILED);
} else {
UsersResponse response = new UsersResponse();
List<UserResponse> innerList = new ArrayList<UserResponse>();
for (int i = 0; i < userEntities.size(); i++) {
TechGalleryUser user = userEntities.get(i);
UserResponse userResponseItem = new UserResponse();
userResponseItem.setId(user.getId());
userResponseItem.setName(user.getName());
userResponseItem.setEmail(user.getEmail());
userResponseItem.setPhoto(user.getPhoto());
innerList.add(userResponseItem);
}
response.setUsers(innerList);
return response;
}
}
/**
* Gets a TechGalleryUser by id.
*
* @param id
* the user's id
* @throws NotFoundException
* if the user is not found
*/
@Override
public TechGalleryUser getUser(final Long id) throws NotFoundException {
TechGalleryUser userEntity = userDao.findById(id);
// if user is null, return a not found exception
if (userEntity == null) {
throw new NotFoundException(i18n.t("No user was found."));
} else {
return userEntity;
}
}
/**
* Adds a new user to Tech Gallery.
*
* @param user
* the TechGallery user to be added
* @return added user
* @throws BadRequestException
* when the user email parameter is missing
*/
@Override
public TechGalleryUser addUser(final TechGalleryUser user) throws BadRequestException {
if (!userDataIsValid(user)) {
throw new BadRequestException(i18n.t("User's email cannot be blank."));
} else {
userDao.add(user);
UserProfileServiceImpl.getInstance().createProfile(user);
return user;
}
}
/**
* This method should be executed whenever a user logs in It check whether the
* user exists on TG's datastore and create them, if not. It also checks if
* the user's email has been changed and update it, in case it was changed.
*
* @param user
* A Google AppEngine API user
* @return A response with the user data as it is on TG datastore
*
* @throws InternalServerErrorException
* in case something goes wrong
* @throws NotFoundException
* in case the information are not founded
* @throws BadRequestException
* in case a request with problem were made.
* @throws OAuthRequestException
* in case of authentication problem
* @throws IOException
* in case of a IO exception
*/
@Override
public TechGalleryUser handleLogin(Integer timezoneOffset, final User user, HttpServletRequest req)
throws NotFoundException, BadRequestException, InternalServerErrorException, IOException,
OAuthRequestException {
authorize(user);
String userEmail = user.getEmail();
String header = req.getHeader("Authorization");
String accesstoken = header.substring(header.indexOf(' ')).trim(); // "Bearer
// ".length
GoogleCredential credential = new GoogleCredential().setAccessToken(accesstoken);
Plus plus = new Plus.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName(i18n.t("Tech Gallery")).build();
Person person = plus.people().get("me").execute();
TechGalleryUser tgUser = userDao.findByGoogleId(user.getUserId());
// Couldn't find by googleID. Try email
if (tgUser == null) {
tgUser = userDao.findByEmail(userEmail);
}
// Ok, we couldn't find it. Create it.
if (tgUser == null) {
tgUser = new TechGalleryUser();
}
updateUserInformation(user, person, tgUser);
tgUser.setTimezoneOffset(timezoneOffset);
addUser(tgUser);
log.info("User " + tgUser.getName() + " added/updated");
return tgUser;
}
/**
* Updates current Tech Gallery user information with user data found on
* Google.
*
* @param user
* Google user
* @param person
* Google Plus person information
* @param tgUser
* Tech Gallery user
*/
private void updateUserInformation(final User user, Person person, TechGalleryUser tgUser) {
String plusEmail = user.getEmail();
String plusPhoto = person.getImage().getUrl();
String plusName = person.getDisplayName();
String currentEmail = tgUser.getEmail();
String currentPhoto = tgUser.getPhoto();
String currentName = tgUser.getName();
if (currentEmail == null || !currentEmail.equals(plusEmail)) {
tgUser.setEmail(plusEmail);
}
if (currentPhoto == null || !currentPhoto.equals(plusPhoto)) {
tgUser.setPhoto(plusPhoto);
}
if (currentName == null || !currentName.equals(plusName)) {
tgUser.setName(plusName);
}
if (tgUser.getGoogleId() == null) {
tgUser.setGoogleId(user.getUserId());
}
}
/**
* Updates a user, with validation.
*
* @throws BadRequestException
* in case of a missing parameter
* @return the updated user
*/
@Override
public TechGalleryUser updateUser(final TechGalleryUser user) throws BadRequestException {
if (!userDataIsValid(user) && user.getId() != null) {
throw new BadRequestException(i18n.t("User's email cannot be blank."));
} else {
userDao.update(user);
return user;
}
}
/**
* GET for getting an user by its login.
*
* @param login
* the user's login
* @return the user found
*/
@Override
public TechGalleryUser getUserByLogin(final String login) throws NotFoundException {
TechGalleryUser userEntity = userDao.findByLogin(login);
if (userEntity == null) {
throw new NotFoundException(OPERATION_FAILED);
} else {
return userEntity;
}
}
/**
* Finds a TechGalleryUser by his/her email.
*
* @param email
* the user's email
* @throws NotFoundException
* if the user is not found
*/
@Override
public TechGalleryUser getUserByEmail(final String email) throws NotFoundException {
TechGalleryUser tgUser = userDao.findByEmail(email);
if (tgUser == null) {
throw new NotFoundException(ValidationMessageEnums.USER_NOT_EXIST.message());
} else {
return tgUser;
}
}
/**
* Checks if user exists on provider, syncs with tech gallery's datastore. If
* user exists, adds to TG's datastore (if not there). Returns the user.
*
* @param userLogin
* userLogin
* @return the user saved on the datastore
* @throws InternalServerErrorException
* in case something goes wrong
* @throws NotFoundException
* in case the information are not founded
* @throws BadRequestException
* in case a request with problem were made.
* @return user sinchronized in provider.
*/
@Override
public TechGalleryUser getUserSyncedWithProvider(final String userLogin)
throws NotFoundException, InternalServerErrorException, BadRequestException {
TechGalleryUser tgUser = null;
TechGalleryUser userResp = getUserFromProvider(userLogin);
tgUser = userDao.findByEmail(getUserFromProvider(userLogin).getEmail());
if (tgUser == null) {
tgUser = new TechGalleryUser();
tgUser.setEmail(userResp.getEmail());
tgUser.setName(userResp.getName());
tgUser = addUser(tgUser);
// Key<TechGalleryUser> key = userDao.add(tgUser);
// tgUser.setId(key.getId());
}
return tgUser;
}
/**
* GET Calls the provider API passing a login to obtain user information.
*
* @param userlogin
* the user login to pass to the provider API
* @throws NotFoundException
* in case the user is not found on provider
* @throws BadRequestException
* in case of JSON or URL error
* @throws InternalServerErrorException
* if any IO exceptions occur
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public TechGalleryUser getUserFromProvider(final String userLogin)
throws NotFoundException, BadRequestException, InternalServerErrorException {
TechGalleryUser tgUser = new TechGalleryUser();
Map<String, Object> providerResponse = peopleApiConnect(userLogin, PEOPLE_ENDPOINT_PROFILE);
HashMap<String, Object> userData = (LinkedHashMap) providerResponse.get("personal_info");
tgUser.setEmail((String) userData.get("email"));
tgUser.setName((String) userData.get("name"));
return tgUser;
}
/**
* GET Calls the provider API passing a login to obtain a list of users
* information.
*
* @param string
* to search on provider by name or login
*
* @throws NotFoundException
* in case the user is not found on provider
* @throws BadRequestException
* in case of JSON or URL error
* @throws InternalServerErrorException
* if any IO exceptions occur
*/
@SuppressWarnings("unchecked")
@Override
public List<UserResponse> getUsersByPartialLogin(String userLogin)
throws NotFoundException, BadRequestException, InternalServerErrorException {
String userLoginFormatted = userLogin + "*";
List<UserResponse> techUsers = (List<UserResponse>) syncCache.get(userLoginFormatted);
if (techUsers == null) {
techUsers = new ArrayList<>();
Map<String, Object> map = peopleApiConnect(userLoginFormatted, PEOPLE_ENDPOINT_SEARCH);
ArrayList<?> peopleApiResponse = (ArrayList<?>) map.get("data");
for (int index = 0; index < peopleApiResponse.size(); index++) {
ArrayList<?> peopleApiUser = (ArrayList<?>) peopleApiResponse.get(index);
final String peopleNameLowerCase = peopleApiUser.get(INDEX_PEOPLE_API_NAME).toString().toLowerCase();
final String peopleLoginLowerCase = peopleApiUser.get(INDEX_PEOPLE_API_LOGIN).toString().toLowerCase();
if (peopleNameLowerCase.contains(userLogin.toLowerCase())
|| peopleLoginLowerCase.contains(userLogin.toLowerCase())) {
TechGalleryUser foundUser = userDao
.findByEmail((String) peopleApiUser.get(INDEX_PEOPLE_API_LOGIN) + EMAIL_DOMAIN);
UserResponse tgUser = new UserResponse();
if (foundUser != null) {
tgUser.setEmail(foundUser.getEmail().split("@")[0]);
tgUser.setName(foundUser.getName());
tgUser.setPhoto(foundUser.getPhoto());
} else {
tgUser.setEmail((String) peopleApiUser.get(INDEX_PEOPLE_API_LOGIN));
tgUser.setName((String) peopleApiUser.get(INDEX_PEOPLE_API_NAME));
tgUser.setPhoto(null);
}
techUsers.add(tgUser);
}
}
syncCache.put(userLoginFormatted, techUsers, memCacheTimeExpliration);
}
return techUsers;
}
@SuppressWarnings("unchecked")
private Map<String, Object> peopleApiConnect(final String userLogin, final String urlEndPoint)
throws NotFoundException, BadRequestException, InternalServerErrorException {
String fullRequest = urlEndPoint + userLogin;
if (urlEndPoint.equals(PEOPLE_ENDPOINT_PROFILE)) {
fullRequest += "?format=json";
}
try {
InputStream resourceStream = UserServiceTGImpl.class.getClassLoader()
.getResourceAsStream("people_basic_auth.txt");
String auth = convertStreamToString(resourceStream);
URL url = new URL(fullRequest);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", auth);
ObjectMapper mapper = new ObjectMapper();
if (conn.getResponseCode() == 200) {
Map<String, Object> providerResponse = mapper.readValue(conn.getInputStream(), Map.class);
return providerResponse;
} else {
throw new NotFoundException(i18n.t("User not found"));
}
} catch (JsonParseException e) {
throw new BadRequestException(OPERATION_FAILED);
} catch (MalformedURLException e) {
throw new BadRequestException(e.getMessage());
} catch (IOException e) {
log.info("An internal server error ocurred!");
log.info(e.getMessage());
throw new InternalServerErrorException(i18n.t("An internal server error ocurred."));
}
}
@SuppressWarnings("resource")
private static String convertStreamToString(InputStream is) {
Scanner scanner = new Scanner(is).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
}
/**
* Validates user data.
*
* @param user
* the TechGalleryUser entity
* @return true if data is valid, false otherwise
*/
private static boolean userDataIsValid(TechGalleryUser user) {
// if user is null, return a bad request exception
if (user == null) {
return false;
}
String userName = user.getName();
String userEmail = user.getEmail();
// user name cannot be blank
if (userName == null || userName.replaceAll("\\s", "").equals("")) {
return false;
}
// user email cannot be blank
if (userEmail == null || userEmail.replaceAll("\\s", "").equals("")) {
return false;
}
return true;
}
@Override
public TechGalleryUser getUserByGoogleId(String googleId) {
return userDao.findByGoogleId(googleId);
}
/*
* (non-Javadoc)
*
* @see
* com.ciandt.techgallery.service.UserServiceTG#saveUserPreference(java.lang.
* Boolean, com.google.appengine.api.users.User)
*/
@Override
public TechGalleryUser saveUserPreference(Boolean postGooglePlusPreference, User user)
throws NotFoundException, BadRequestException, InternalServerErrorException, IOException, OAuthRequestException {
validateUser(user);
TechGalleryUser techUser = userDao.findByEmail(user.getEmail());
if (postGooglePlusPreference != null) {
techUser.setPostGooglePlusPreference(postGooglePlusPreference);
userDao.update(techUser);
}
return techUser;
}
/*
* (non-Javadoc)
*
* @see com.ciandt.techgallery.service.UserServiceTG#validateUser(com.google.
* appengine.api.users.User)
*/
@Override
public TechGalleryUser validateUser(User user)
throws BadRequestException, NotFoundException, InternalServerErrorException {
if (user == null || user.getUserId() == null || user.getUserId().isEmpty()) {
throw new BadRequestException(ValidationMessageEnums.USER_GOOGLE_ENDPOINT_NULL.message());
}
final TechGalleryUser techUser = getUserByGoogleId(user.getUserId());
if (techUser == null) {
throw new NotFoundException(ValidationMessageEnums.USER_NOT_EXIST.message());
}
return techUser;
}
/**
* @Param user
* info about user from google
* @throws BadRequestException
* @throws NotFoundException
* @throws GeneralSecurityException
*/
@Override
public void authorize(final User user) throws OAuthRequestException {
if(user == null) {
throw new OAuthRequestException(i18n.t("Authorization error"));
}
}
}